﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Runtime.Serialization.Json;
using System.Text;
using CUETools.Codecs;
using CUETools.Codecs.FLAKE;
using GoogleSpeechRecognition;

namespace GoogleSpeechRecognition
{
    public class SpeechToText
    {
        private string endpointAddress;

        public SpeechToText()
            : this("https://www.google.com/speech-api/v1/recognize?xjerr=1&client=chromium&clipSize=167", CultureInfo.CurrentCulture)
        {
        }

        public SpeechToText(string endpointAddress, CultureInfo culture)
        {
            this.endpointAddress = endpointAddress + culture.Name;
        }

        public SpeechToText(string lang)
        {
            this.endpointAddress = "https://www.google.com/speech-api/v1/recognize?xjerr=1&client=chromium&lang=" + lang;
        }

        public IEnumerable<TextResponse> Recognize(List<string> filepaths)
        {
            List<TextResponse> speechResponses = new List<TextResponse>();

            if (filepaths.Count > 0)
            {
                foreach (string path in filepaths)
                {
                    if (File.Exists(path))
                    {
                        IEnumerable<TextResponse> hypothesis = this.Recognize(path, false, true);
                        foreach (TextResponse txt in hypothesis)
                            speechResponses.Add(txt);
                        File.Delete(path);
                    }
                }
            }
            return (speechResponses);
        }

        private IEnumerable<TextResponse> SplitAndRecognize(string filepath)
        {
            int chunkSize = 800000;
            byte[] chunkData = new byte[chunkSize];
            int bytesRead;

            List<string> files = new List<string>();
            string nameA = "splitted_p";
            int nameB = 1;
            string nameC = ".wav";

            using (NAudio.Wave.WaveFileReader reader = new NAudio.Wave.WaveFileReader(filepath))
            {
                do
                {
                    bytesRead = reader.Read(chunkData, 0, chunkData.Length);
                        
                    string name = nameA + Convert.ToString(nameB) + nameC;
                    NAudio.Wave.WaveFileWriter writer = new NAudio.Wave.WaveFileWriter(name, reader.WaveFormat);
                    writer.Write(chunkData, 0, chunkData.Length);
                    writer.Dispose();
                    writer.Close();
                    files.Add(name);
                    nameB += 1;
                }
                while (bytesRead != 0);
            }
            return (this.Recognize(files));
        }

        public IEnumerable<TextResponse> Recognize(string filepath, bool convert = true, bool alreadySplitted = false)
        {
            try
            {
                if (convert == true)
                {
                    try
                    {
                        string extension = Path.GetExtension(filepath).ToLower();
                        if (extension != null && extension.Length == 4)
                        {
                            if (extension.CompareTo(".mp3") == 0)
                                AudioConverter.Mp3Converter.Instance.Convert(filepath, "temp.wav", 16000, 16, 2);
                            if (extension.CompareTo(".wav") == 0)
                                AudioConverter.WavConverter.Instance.Convert(filepath, "temp.wav", 16000, 16, 2);
                        }
                    }
                    catch (Exception err)
                    {
                        Console.WriteLine(err.Message);
                    }
                }
                FileInfo f = new FileInfo(convert == true ? "temp.wav" : filepath);
                if (alreadySplitted == false && f.Length > 800000)
                {
                    IEnumerable<TextResponse> result = this.SplitAndRecognize(convert == true ? "temp.wav" : filepath);
                    if (convert == true)
                        File.Delete("temp.wav");
                    return (result);
                }
                else
                {
                    using (var contentToRecognize = new FileStream((convert == true ? "temp.wav" : filepath), FileMode.Open))
                    {
                        var request = (HttpWebRequest)WebRequest.Create(this.endpointAddress + "&maxresults=6&pfilter=2");
                        ConfigureRequest(request);
                        var requestStream = request.GetRequestStream();
                        ConvertToFlac(contentToRecognize, requestStream);
                        WebResponse response = request.GetResponse();
                        var speechResponses = new List<TextResponse>();
                        using (var responseStream = response.GetResponseStream())
                        {
                            // output will be something like {"status":0,"id":"2526dd613c874c321bf9abecd5331ed1-1","hypotheses":[{"utterance":"this is a test this is a test","confidence":0.92601025},{"utterance":"this is a test this is the test"},{"utterance":"this is a test this is a text"},{"utterance":"this is a test this is a test txt"},{"utterance":"this is the test this is a test"},{"utterance":"this is a test this is a test hey"}]}
                            using (var zippedStream = new GZipStream(responseStream, CompressionMode.Decompress))
                            {
                                var recognizedText = JsonUtil.Deserialise<RecognizedText>(zippedStream);
                                speechResponses.AddRange(recognizedText.Hypotheses.Select(hypothesis => new TextResponse { Confidence = hypothesis.Confidence, Utterance = hypothesis.Utterance }));
                            }
                        }
                        response.Close();
                        if (convert == true)
                            File.Delete("temp.wav");
                        return (speechResponses);
                    }
                }
            }
            catch (Exception err)
            {
                Console.WriteLine(err.Message);
            }
            return (new List<TextResponse>());
        }

        public IEnumerable<TextResponse> Recognize(Stream contentToRecognize)
        {
            var request = (HttpWebRequest)WebRequest.Create(this.endpointAddress + "&maxresults=6&pfilter=2");
            ConfigureRequest(request);
            var requestStream = request.GetRequestStream();
            ConvertToFlac(contentToRecognize, requestStream);
            var response = request.GetResponse();
            var speechResponses = new List<TextResponse>();
            using (var responseStream = response.GetResponseStream())
            {
                // output will be something like {"status":0,"id":"2526dd613c874c321bf9abecd5331ed1-1","hypotheses":[{"utterance":"this is a test this is a test","confidence":0.92601025},{"utterance":"this is a test this is the test"},{"utterance":"this is a test this is a text"},{"utterance":"this is a test this is a test txt"},{"utterance":"this is the test this is a test"},{"utterance":"this is a test this is a test hey"}]}
                using (var zippedStream = new GZipStream(responseStream, CompressionMode.Decompress))
                {
                    var recognizedText = JsonUtil.Deserialise<RecognizedText>(zippedStream);
                    speechResponses.AddRange(recognizedText.Hypotheses.Select(hypothesis => new TextResponse {Confidence = hypothesis.Confidence, Utterance = hypothesis.Utterance}));
                }
            }
            response.Close();
            return speechResponses;
        }

        private static void ConfigureRequest(HttpWebRequest request)
        {
            request.KeepAlive = true;
            request.SendChunked = true;
            request.ContentType = "audio/x-flac; rate=16000";
            request.UserAgent = "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.874.121 Safari/535.2";
            request.Headers.Set(HttpRequestHeader.AcceptEncoding, "gzip,deflate,sdch");
            request.Headers.Set(HttpRequestHeader.AcceptLanguage, "en-GB,en-US;q=0.8,en;q=0.6");
            request.Headers.Set(HttpRequestHeader.AcceptCharset, "ISO-8859-1,utf-8;q=0.7,*;q=0.3");
            request.Method = "POST";
        }

        private void ConvertToFlac(Stream sourceStream, Stream destinationStream)
        {
            var audioSource = new WAVReader(null, sourceStream);
            try
            {
                if (audioSource.PCM.SampleRate != 16000)
                {
                    throw new InvalidOperationException("Incorrect frequency - WAV file must be at 16 KHz.");
                }
                var buff = new AudioBuffer(audioSource, 0x10000);
                var flakeWriter = new FlakeWriter(null, destinationStream, audioSource.PCM);
                flakeWriter.CompressionLevel = 8;
                while (audioSource.Read(buff, -1) != 0)
                {
                    flakeWriter.Write(buff);
                }
                flakeWriter.Close();
            }
            finally
            {
                audioSource.Close();
            }
        }
    }

    public class JsonUtil
    {
        public static T Deserialise<T>(Stream stream)
        {
            T obj = Activator.CreateInstance<T>();
            DataContractJsonSerializer serializer = new DataContractJsonSerializer(obj.GetType());
            obj = (T)serializer.ReadObject(stream);
            return (obj);
        }
    }
}
